package aduros.util {

import flash.events.Event;

/**
 * Functional programming elements for AS3.
 * TODO: Document me
 */
public class F
{
    public static const _ :Object = new Object();

    public static function equals (a :*, b :*) :Boolean
    {
        return a == b;
    }

    public static function plus (a :*, b :*) :*
    {
        return a + b;
    }

    public static function lessThan (a :*, b :*) :Boolean
    {
        return a < b;
    }

    public static function lessEqualThan (a :*, b :*) :Boolean
    {
        return a <= b;
    }

    /**
     * eg: F.partial(F.equals, 5)
     * eg: F.partial(log.info, F._, "foo", foo)
     */
    public static function partial (f :Function, ... left) :Function
    {
        if (left.indexOf(F._) == -1) {
            // Trade some up front creation time for execution speed later
            return function (... right) :* {
                return adapt(f).apply(undefined, left.concat(right));
            }

        } else {
            return function (... right) :* {
                return adapt(f).apply(undefined,
                    left.map(function (arg :*, index :int, arr :Array) :* {
                        return (arg === F._) ? right.shift() : arg;
                    }).concat(right));
            }
        }
    }

    public static function callback (f: Function, ... args) :Function
    {
        return function (... _) :* {
            return f.apply(undefined, args);
        }
    }

    public static function compose (f :Function, g :Function) :Function
    {
        return function (... rest) :* {
            return f(g.apply(undefined, rest));
        }
    }

    public static function konst (x :*) :Function
    {
        return function (... _) :* {
            return x;
        }
    }

    public static function id (x :*) :*
    {
        return x;
    }

    public static function foldl (f :Function, e :*, xs :Array) :*
    {
        for each (var x :* in xs) {
            e = f(e, x);
        }

        return e;
    }

    /**
     * Return a var-args function that will attempt to pass only the arguments accepted by the
     * passed-in function. Does not work if the passed-in function is varargs, and anyway
     * then you don't need adapting, do you?
     *
     * @see com.threerings.util.Util#adapt()
     */
    public static function adapt (f :Function) :Function
    {
        return function (... args) :* {
            args.length = f.length; // fit the args to the fn, filling in 'undefined' if growing
            return f.apply(undefined, args);
        }
    }

    public static function map (f :Function, xs :Array) :Array
    {
        return xs.map(adapt(f));
    }

    public static function filter (f :Function, xs :Array) :Array
    {
        return xs.filter(adapt(f));
    }

    public static function justOnce (handler :Function) :Function
    {
        return function (event :Event) :void {
            event.target.removeEventListener(event.type, arguments.callee);
            handler(event);
        }
    }
}

}
